package com.ctshk.rpc.localfun.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ctshk.common.constant.ElasticSearchConstant;
import com.ctshk.common.constant.RedisConstants;
import com.ctshk.common.dto.Result;
import com.ctshk.common.elasticsearch.config.ElasticSearchConfiguration;
import com.ctshk.common.enums.*;
import com.ctshk.common.enums.Currency;
import com.ctshk.common.exception.BusinessException;
import com.ctshk.common.model.PageResponse;
import com.ctshk.common.model.TokenUser;
import com.ctshk.common.utils.Assist;
import com.ctshk.common.utils.CalcIntegralUtil;
import com.ctshk.common.utils.DateUtil;
import com.ctshk.common.utils.EntityUtil;
import com.ctshk.rpc.cms.dto.NoticeDTO;
import com.ctshk.rpc.cms.dto.NoticePositionListDTO;
import com.ctshk.rpc.cms.service.INoticePositionService;
import com.ctshk.rpc.hotel.dto.pc.HotelHotCityDTO;
import com.ctshk.rpc.hotel.service.IHotelH5Service;
import com.ctshk.rpc.localfun.dto.*;
import com.ctshk.rpc.localfun.dto.es.*;
import com.ctshk.rpc.localfun.dto.es.LocalFunReservationGetOrderDTO;
import com.ctshk.rpc.localfun.entity.*;
import com.ctshk.rpc.localfun.entity.es.LocalFunProductCacheUp;
import com.ctshk.rpc.localfun.enums.LocalFunSortType;
import com.ctshk.rpc.localfun.enums.PCHomeTitleEnum;
import com.ctshk.rpc.localfun.enums.ProductCenterEnum;
import com.ctshk.rpc.localfun.mapper.*;
import com.ctshk.rpc.localfun.req.*;
import com.ctshk.rpc.localfun.req.cache.LocalFunOrderReq;
import com.ctshk.rpc.localfun.req.cache.LocalFunProductCacheOpsReq;
import com.ctshk.rpc.localfun.req.cache.RemainderNumOpsReq;
import com.ctshk.rpc.localfun.service.ILocalFunAppProductService;
import com.ctshk.rpc.localfun.service.ILocalFunCostPriceService;
import com.ctshk.rpc.localfun.util.NumberUtil;
import com.ctshk.rpc.season.req.AppSearchFixedProductReq;
import com.ctshk.rpc.season.service.ISeasonFixedProductService;
import com.ctshk.rpc.system.dto.LocalFunIcon;
import com.ctshk.rpc.system.dto.LocalFunIncoDTO;
import com.ctshk.rpc.system.dto.UserDTO;
import com.ctshk.rpc.system.req.MainDataLocalFunAddReq;
import com.ctshk.rpc.system.service.IMainDataCurrencyService;
import com.ctshk.rpc.system.service.IMainDataLocalFunService;
import com.ctshk.rpc.system.service.ISysDepartmentService;
import com.ctshk.rpc.system.service.ISysUserService;
import com.ctshk.rpc.tour.req.es.TripSearchReq;
import com.ctshk.rpc.tour.service.ISeriesTripAppService;
import com.ctshk.rpc.user.dto.AppUserDTO;
import com.ctshk.rpc.user.req.UserFootPointReq;
import com.ctshk.rpc.user.service.IUserFootPointService;
import com.ctshk.rpc.user.service.IUserService;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedCardinality;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Description C端服務
 * @Author wansi <gaylen15527491065@yeah.net>
 * @Date 2021/4/12
 */
@Slf4j
@DubboService
public class LocalFunAppProductServiceImpl extends ServiceImpl<LocalFunProductMapper, LocalFunProduct> implements ILocalFunAppProductService {

    @Autowired
    LocalFunProductMapper localFunProductMapper;
    @Autowired
    LocalFunProductSaleRecordMapper localFunProductSaleRecordMapper;
    @Autowired
    ILocalFunCostPriceService iLocalFunCostPriceService;
    @Autowired
    LocalFunProductCostStrategyMapper localFunProductCostStrategyMapper;
    @Autowired
    LocalFunProductCostStrategyVaildDateMapper localFunProductCostStrategyVaildDateMapper;
    @Autowired
    LocalFunProductCostStrategySpecAttrMapper localFunProductCostStrategySpecAttrMapper;
    @Autowired
    LocalFunProductChannelOnlineSelfPlatformMapper localFunProductChannelOnlineSelfPlatformMapper;
    @Autowired
    LocalFunProductCostStrategySpecStockMapper localFunProductCostStrategySpecStockMapper;
    @Autowired
    LocalFunElasticSearchMapper localFunElasticSearchMapper;
    @Autowired
    RedisTemplate<String, String> redisTemplate;
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Autowired
    RedissonClient redissonClient;
    //汇率服务
    @DubboReference
    private IMainDataCurrencyService mainDataCurrencyService;
    @DubboReference
    private ISysDepartmentService iSysDepartmentService;
    @DubboReference
    private ISysUserService iSysUserService;
    @DubboReference
    private IUserFootPointService userFootPointService;
    @DubboReference
    private ISeriesTripAppService iSeriesTripAppService;
    @DubboReference
    private ISeasonFixedProductService seasonFixedProductService;
    @DubboReference
    private IHotelH5Service hotelH5Service;
    @Autowired
    private LocalFunProductCostStrategySpecStockMapper costStrategySpecStockMapper;
    @DubboReference
    private IMainDataLocalFunService iMainDataLocalFunService;
    @Autowired
    private LocalFunProductSaleRecordMapper saleRecordMapper;
    @DubboReference
    private IUserService userService;
    @DubboReference
    private INoticePositionService noticePositionService;
    @Autowired
    private LocalFunProductSaleStrategyMapper localFunProductSaleStrategyMapper;
    /**
     * 当地玩乐首页入口列表
     */
    private static final String HOME_TITLE_KEY = "localFun_home_title:%s:%s";

    /**
     * 当地玩乐首页接口
     * gzl-wansi
     * @param req
     * @return
     */
    @Override
    public Result<LocalFunHomeDto> queryProductList(PCLocalFunQueryReq req) {
        StopWatch stopWatch = new StopWatch();

        //当地玩乐
        stopWatch.start("查询[当地玩乐]首页当地玩乐列表");
        LocalFunSearchResDTO cache = null;
        try {
            cache = getLocalFunSearchRes(req);
        } catch (Exception e) {
            log.error("[当地玩乐首页]-首页当地玩乐列表检索错误：{}",e.getMessage());
        }
        stopWatch.stop();


        //精选旅客团
        stopWatch.start("查询[当地玩乐]首页精选旅行团列表");
        com.ctshk.rpc.tour.dto.cache.TripSearchResDTO tripsByParams = null;
        try {
            TripSearchReq tripSearchReq = new TripSearchReq();
            tripSearchReq.setChannelId(Long.valueOf(req.getChannelId()));
            tripSearchReq.setTourTypeCode("-1");
            tripSearchReq.setPageNum(1);
            tripSearchReq.setPageSize(4);
            if(StringUtil.isNotEmpty(req.getCityName())){
                tripSearchReq.setKeyword(req.getCityName());
            }
            tripSearchReq.setDestinationId(req.getCityId());
            tripsByParams = iSeriesTripAppService.getTripsByParams(tripSearchReq);
        } catch (Exception e) {
            log.error("[当地玩乐首页-精选旅客团]-首页旅行团列表检索错误：{}",e.getMessage());
        }
        stopWatch.stop();

        //热门套票
        stopWatch.start("查询[当地玩乐]首页热门套票列表");
        Result<PageResponse<com.ctshk.rpc.season.dto.AppFixedSeasonProductDTO>> pageResponseResult = null;
        try {
            AppSearchFixedProductReq appSearchFixedProductReq = new AppSearchFixedProductReq();
            appSearchFixedProductReq.setAdult(1);
            appSearchFixedProductReq.setChannelId(3L);
            appSearchFixedProductReq.setPageNo(1);
            appSearchFixedProductReq.setPageSize(4);
            appSearchFixedProductReq.setArrCityId(req.getCityId());
            pageResponseResult = seasonFixedProductService.onLineSeasonSearch(null, appSearchFixedProductReq);
        } catch (Exception e) {
            log.error("[当地玩乐首页-热门套票]-首页热门套票列表检索错误：{}",e.getMessage());
        }
        stopWatch.stop();

        //热门酒店
        stopWatch.start("查询[当地玩乐]首页热门酒店列表");
        List<HotelHotCityDTO> hotelHotCityDTOS = null;
        try {
            hotelHotCityDTOS = hotelH5Service.queryRecommendationsPc();
        } catch (Exception e) {
            log.error("[当地玩乐首页-热门酒店]-首页热门酒店列表检索错误：{}",e.getMessage());
        }
        stopWatch.stop();
        log.info("Query LocalHome cost:{}",stopWatch.prettyPrint());

        LocalFunHomeDto localFunHomeDto = new LocalFunHomeDto();
        localFunHomeDto.setLocalFunCache(cache);
        localFunHomeDto.setSeasonProductCache(getSeasonCache(pageResponseResult));
        localFunHomeDto.setTripSearchResCache(getTripSearchCache(tripsByParams));
        localFunHomeDto.setHotelHotsCache(getHotel(hotelHotCityDTOS,req));
        localFunHomeDto.setHomeTitle(getHomeTitle(req));

        //设置公告信息
        try {
            Result<NoticePositionListDTO> ddwlNotice = noticePositionService.getNoticePositionByCode("DDWL_NOTICE");
            if(ddwlNotice != null && ddwlNotice.getData() != null){
                List<NoticeDTO> noticeDTOS = ddwlNotice.getData().getNoticeDTOS();
                if(CollectionUtils.isNotEmpty(noticeDTOS)){
                    NoticeDTO noticeDTO = noticeDTOS.stream().findFirst().orElse(null);
                    if(noticeDTO != null){
                        localFunHomeDto.setTitle(noticeDTO.getTitle());
                        localFunHomeDto.setContent(noticeDTO.getNoticeContent());
                    }
                }
            }
        } catch (Exception e) {
            log.error("获取公告信息失败");
        }
        return Result.success(localFunHomeDto);
    }

    /**
     * 获取当地玩乐首页-入口列表
     * @param req
     * @return
     */
    private List<LocalFunHomeTitleDTo> getHomeTitle(PCLocalFunQueryReq req) {
        String obj = redisTemplate.opsForValue().get(String.format(HOME_TITLE_KEY, req.getChannelId(), req.getCityId()));
        if(StringUtil.isNotEmpty(obj)){
            return JSON.parseArray(obj,LocalFunHomeTitleDTo.class);
        }
        List<LocalFunHomeTitleDTo> list = Lists.newArrayListWithExpectedSize(10);
        //机票
        LocalFunHomeTitleDTo airDto = getAirTitleDTos(req);
        list.add(airDto);

        //酒店
        LocalFunHomeTitleDTo hotelDto = getHotelTitleDTos(req);
        list.add(hotelDto);

        //火车票
        LocalFunHomeTitleDTo trainDto = getTrainTitleDTos(req);
        list.add(trainDto);

        //船票暂时不支持，后期补上
        //门票
        LocalFunHomeTitleDTo scenicDto = getScenicTitleDTos(req);
        list.add(scenicDto);

        //巴士票
        LocalFunHomeTitleDTo busDto = getBusTitleDTos(req);
        list.add(busDto);

        //旅行团
        LocalFunHomeTitleDTo tripDto = getTripTitleDTos(req);
        list.add(tripDto);

        //定制旅游
        LocalFunHomeTitleDTo planDto = getPlanTitleDTos(req);
        list.add(planDto);

        //获取当地玩乐产品信息
        list.addAll(addLocalFunHomeTitleDTos(req));

        //自由行
        LocalFunHomeTitleDTo randomDto = getRandomTitleDTos(req);
        list.add(randomDto);

        redisTemplate.opsForValue().set(String.format(HOME_TITLE_KEY, req.getChannelId(), req.getCityId()),JSON.toJSONString(list),1,TimeUnit.MINUTES);
        return list;
    }

    private LocalFunHomeTitleDTo getFixTicketTitle(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.TICKET.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.TICKET.getOrder());
        return airDto;
    }

    /**
     * 自由行入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getRandomTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.RANDOM.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.RANDOM.getOrder());
        return airDto;
    }

    /**
     * 定制旅游入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getPlanTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.PLAN.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.PLAN.getOrder());
        return airDto;
    }

    /**
     * 旅行团入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getTripTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.TRIP.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.TRIP.getOrder());
        return airDto;
    }

    /**
     * 巴士入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getBusTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.BUS.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.BUS.getOrder());
        return airDto;
    }

    /**
     * 门票入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getScenicTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.SCENIC.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.SCENIC.getOrder());
        return airDto;
    }

    /**
     * 火车票入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getTrainTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.TRAIN.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.TRAIN.getOrder());
        return airDto;
    }

    /**
     * 酒店入口配置
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getHotelTitleDTos(PCLocalFunQueryReq req) {
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.HOTEL.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.HOTEL.getOrder());
        return airDto;
    }

    /**
     * 当地玩乐入口-机票是否有有效产品
     * @param req
     * @return
     */
    private LocalFunHomeTitleDTo getAirTitleDTos(PCLocalFunQueryReq req) {
//        AirTicketsListReq airTicketsListReq = new AirTicketsListReq();
//        airTicketsListReq.setArrCityId(req.getCityId());
//        airTicketsListReq.setChannelId(req.getChannelId());
//        Result<PageResponse<AirProductListDTO>> ticketList = iAirProductAppService.getTicketList(airTicketsListReq);
//        if(ticketList != null && ticketList.getData() != null && CollectionUtil.isEmpty(ticketList.getData().getRecords())){
//            LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
//            airDto.setName(PCHomeTitleEnum.AIR.getModuleName());
//            airDto.setIcon(null);
//            airDto.setSortNum(PCHomeTitleEnum.AIR.getOrder());
//            return airDto;
//        }
        LocalFunHomeTitleDTo airDto = new LocalFunHomeTitleDTo();
        airDto.setName(PCHomeTitleEnum.AIR.getModuleName());
        airDto.setIcon(null);
        airDto.setSortNum(PCHomeTitleEnum.AIR.getOrder());
        return airDto;
    }

    private List<LocalFunHomeTitleDTo> addLocalFunHomeTitleDTos(PCLocalFunQueryReq req) {
        List<LocalFunHomeTitleDTo> list = Lists.newArrayListWithExpectedSize(10);
        try {
            Map<Long, LocalFunIcon> funSortMapping = iMainDataLocalFunService.getFunSortMapping();
            //查询当地玩乐有效产品数
            List<LocalFunTypeDto> funTypeDtos = localFunProductMapper.getProductCount(req.getCityId(),req.getChannelId(), LocalDate.now());
            if(CollectionUtil.isNotEmpty(funTypeDtos)){
                Map<Long, List<LocalFunTypeDto>> funTypeMap = funTypeDtos.stream().collect(Collectors.groupingBy(LocalFunTypeDto::getFunType));
                funTypeMap.forEach((k,v)->{
                    LocalFunIcon localFunIcon = funSortMapping.get(k);
                    if(localFunIcon != null){
                        LocalFunHomeTitleDTo dTo = new LocalFunHomeTitleDTo();
                        dTo.setIcon(getIconUrl(localFunIcon.getPhotos()));
                        dTo.setFunType(k);
                        dTo.setName(localFunIcon.getName());
                        dTo.setSortNum(PCHomeTitleEnum.LOCAL.getOrder());
                        dTo.setChildSort(localFunIcon.getSort());
                        list.add(dTo);
                    }
                });
            }
        } catch (Exception e) {
           log.error("获取当地玩乐首页-入口列表错误：{}",e.getMessage());
        }
        return list.stream().sorted(Comparator.comparing(LocalFunHomeTitleDTo::getChildSort))
                .collect(Collectors.toList());
    }

    private String getIconUrl(String photos) {
        try {
            List<IconDTO> iconDTOS = JSON.parseArray(photos, IconDTO.class);
            if(CollectionUtil.isNotEmpty(iconDTOS)){
                IconDTO iconDTO = iconDTOS.get(0);
                return iconDTO != null ? iconDTO.getUrl() : "";
            }
        } catch (Exception e) {
           log.error("图片转换异常：{}",e.getMessage());
        }
        return "";
    }

    private LocalFunIncoDTO getLocalFunIncoInfo(Long funType){
        MainDataLocalFunAddReq localFunAddReq =new MainDataLocalFunAddReq();
        localFunAddReq.setId(funType);
        Result<PageResponse<LocalFunIncoDTO>> list = iMainDataLocalFunService.list(localFunAddReq, null);
        PageResponse<LocalFunIncoDTO> data = list.getData();
        if(data != null && CollectionUtil.isNotEmpty(data.getRecords())){
            LocalFunIncoDTO dto = data.getRecords().stream().findFirst().orElse(null);
            return dto;
        }
        return null;
    }

    /**
     * 获取热门酒店
     * @param hotelHotCityDTOS
     * @return
     */
    private List<com.ctshk.rpc.localfun.dto.es.HotelHotDTO> getHotel(List<HotelHotCityDTO> hotelHotCityDTOS, PCLocalFunQueryReq req) {
        if(CollectionUtils.isEmpty(hotelHotCityDTOS) || StringUtils.isEmpty(req.getCityName())){
            return null;
        }
        HotelHotCityDTO hotelHotCityDTO = hotelHotCityDTOS.stream().filter(entity -> req.getCityName().contains(entity.getCity())).findFirst().orElse(null);
        if(hotelHotCityDTO != null){
            List<com.ctshk.rpc.hotel.dto.h5.HotelHotDTO> hotels = hotelHotCityDTO.getHotels();
            if(hotels.size() >=4){
                return EntityUtil.copyList(hotels,com.ctshk.rpc.localfun.dto.es.HotelHotDTO.class).subList(0,4);
            }
            return EntityUtil.copyList(hotels,com.ctshk.rpc.localfun.dto.es.HotelHotDTO.class);


        }
        return null;
    }



    /**
     * 查询当地玩乐产品列表信息
     * @param req
     * @return
     */
    protected LocalFunSearchResDTO getLocalFunSearchRes(PCLocalFunQueryReq req) {
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_INDEX).source(searchSourceBuilder(req));
        LocalFunSearchResDTO localFunSearchResDTO = new LocalFunSearchResDTO();
        // 处理响应结果
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHits hits = response.getHits();
            List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = new ArrayList<>();
            // 反序列化命中记录
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                LocalFunProductCacheUpDTO localFunProductCacheUpDTO = JSON.parseObject(sourceAsString, LocalFunProductCacheUpDTO.class);
                localFunProductCacheUpDTOS.add(localFunProductCacheUpDTO);
            }

            // 解析聚合结果
            Aggregations aggregations = response.getAggregations();
            ParsedStringTerms type_id_aggs = aggregations.get("type_id_agg");
            if (type_id_aggs != null && type_id_aggs.getBuckets().size() > 0) {
                List<LocalFunTypeResDTO> localFunTypeResDTOS = new ArrayList<>();
                // 遍历游玩类型id聚合
                for (Terms.Bucket bucket : type_id_aggs.getBuckets()) {
                    LocalFunTypeResDTO localFunTypeResDTO = new LocalFunTypeResDTO();
                    // 游玩类型id
                    long funType = bucket.getKeyAsNumber().longValue();

                    localFunTypeResDTO.setFunTypeId(funType);
                    // 游玩类型id聚合 -> 游玩类型名称子聚合
                    ParsedStringTerms aggregation = bucket.getAggregations().get("type_name_agg");
                    // 游玩类型名称
                    localFunTypeResDTO.setFunTypeName(aggregation.getBuckets().get(0).getKeyAsString());
//                    localFunTypeResDTO.setSort(aggregation.getBuckets().get(1).getKeyAsNumber().intValue());
                    // 添加游玩类型列表
                    List<LocalFunProductCacheUpDTO> productCaches = localFunProductCacheUpDTOS.stream().filter(item -> item.getFunType().equals(funType)).collect(Collectors.toList());
                    List<LocalFunProductResDTO> localFunProductResDTOS = EntityUtil.copyList(productCaches, LocalFunProductResDTO.class);

                    // 根据请求来源设置展示价格
                    localFunProductResDTOS.forEach(res -> {
                        productCaches.forEach(cache -> { //
                            if (res.getId().equals(cache.getId())) {
                                res.setTitle(cache.getProductName());
                                res.setCoverImage(Objects.isNull(cache.getPhotosJson())?"":getUrl(cache));
                                res.setPrice(ChannelMainData.CHINA_TRAVEL.getId() == req.getChannelId().longValue() ? NumberUtil.getIntegerAmount(new BigDecimal(cache.getPcPrice())) : NumberUtil.getIntegerAmount(new BigDecimal(cache.getH5Price())));
                            }
                        });
                    });

                    //仅限4个
                    localFunProductResDTOS = localFunProductResDTOS.stream().filter(entity->entity.getPrice() != BigDecimal.ZERO).collect(Collectors.toList());
                    if(localFunProductResDTOS.size() >= 4){
                        localFunProductResDTOS = localFunProductResDTOS.subList(0,4);
                    }
                    // 封装检索产品响应对象  localFunProductResDTOS
                    if(CollectionUtil.isNotEmpty(localFunProductResDTOS)){
                        localFunTypeResDTO.setLocalFunProductResDTOS(localFunProductResDTOS);
                        localFunTypeResDTOS.add(localFunTypeResDTO);

                        localFunSearchResDTO.setCityId(productCaches.get(0).getCityId());
                        localFunSearchResDTO.setCityName(productCaches.get(0).getCityName());
                        localFunSearchResDTO.setLocalFunTypeResDTOS(localFunTypeResDTOS);
                    }
                }
            }
        } catch (IOException e) {
            log.error("【业务模块APP服務-查询es产品列表】异常", e);
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
        return localFunSearchResDTO;
    }

    private String getUrl(LocalFunProductCacheUpDTO cache) {
        List<PhotosJson> photosJson = cache.getPhotosJson();
        if(CollectionUtil.isNotEmpty(photosJson)){
            PhotosJson photosJson1 = photosJson.get(0);
            return photosJson1 != null ? photosJson1.getUrl() : "";
        }
        return "";
    }


    private List<Trip> getTripSearchCache(com.ctshk.rpc.tour.dto.cache.TripSearchResDTO tripsByParams) {
        if(tripsByParams == null){
            return null;
        }
        List<com.ctshk.rpc.tour.dto.cache.Trip> trips = tripsByParams.getTrips();
        List<Trip> trips1 = EntityUtil.copyList(trips, Trip.class);
        return trips1;
    }

    private List<com.ctshk.rpc.localfun.dto.es.AppFixedSeasonProductDTO> getSeasonCache(Result<PageResponse<com.ctshk.rpc.season.dto.AppFixedSeasonProductDTO>> pageResponseResult) {
        if(pageResponseResult != null && pageResponseResult.getData() != null && CollectionUtil.isNotEmpty(pageResponseResult.getData().getRecords())){
            List<com.ctshk.rpc.localfun.dto.es.AppFixedSeasonProductDTO> appFixedSeasonProductDTOS = EntityUtil.copyList(pageResponseResult.getData().getRecords(), com.ctshk.rpc.localfun.dto.es.AppFixedSeasonProductDTO.class);
            return appFixedSeasonProductDTOS;
        }
        return null;
    }

    /**
     * 构建检索条件
     *
     * @param req
     * @return
     */
    private SearchSourceBuilder searchSourceBuilder(PCLocalFunQueryReq req) {
        // 入参为空
        if (req == null) throw new BusinessException(SystemError.LOCAL_FUN_18020);

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 前端输入的检索条件
        if (!StringUtils.isEmpty(req.getKey())) {
            boolQuery.must(QueryBuilders.matchQuery("title", req.getKey()));
        }

        // 游玩类型id
        if (req.getFunType() != null && req.getFunType() != 0) {
            boolQuery.filter(QueryBuilders.termQuery("funType", req.getFunType()));
        }

        // 城市id
        if (req.getCityId() != null && req.getCityId() != 0) {
            boolQuery.filter(QueryBuilders.termQuery("cityId", req.getCityId()));
        }
        // 城市名称
//        if (req.getCityId() == null) {
//            boolQuery.filter(QueryBuilders.termQuery("cityName", null == req.getCityName() ? "香港" : req.getCityName()));
//        }
        boolQuery.mustNot(QueryBuilders.termQuery("pcPrice",BigDecimal.ZERO));
        //推荐
        sourceBuilder.sort(new FieldSortBuilder("isRecommend").order(SortOrder.DESC));
        sourceBuilder.query(boolQuery);

        // 聚合游玩类型
        TermsAggregationBuilder type_id_agg = AggregationBuilders.terms("type_id_agg").field("funType");
        type_id_agg.subAggregation(AggregationBuilders.terms("type_name_agg").field("funName"));
        sourceBuilder.aggregation(type_id_agg);

        // 分页
        sourceBuilder.from(req.getPageNo() == null || req.getPageNo() == 0 ? 0 : req.getPageNo() - 1 * req.getPageNum());
        sourceBuilder.size(req.getPageNum() == null || req.getPageNum() == 0? ElasticSearchConstant.PAGE_SIZE : req.getPageNum());

        // 排序
        if (req.getSoldSortType() != null  || req.getPriceSortType() != null){
            if(req.getSort() != null){
                if(LocalFunSortType.BEFORE.getCode().equals(req.getSort())){
                    queryPrice(req, sourceBuilder);
                    querySold(req, sourceBuilder);
                }else {
                    querySold(req, sourceBuilder);
                    queryPrice(req, sourceBuilder);
                }
            }else {
                queryPrice(req, sourceBuilder);
                querySold(req, sourceBuilder);
            }
        } else {
            sourceBuilder.sort(new FieldSortBuilder("isRecommend").order(SortOrder.DESC));
            sourceBuilder.sort(new FieldSortBuilder("saleCount").order(SortOrder.DESC));
            sourceBuilder.sort(new FieldSortBuilder("createDate").order(SortOrder.DESC));
        }
        return sourceBuilder;
    }

    private void queryPrice(PCLocalFunQueryReq req, SearchSourceBuilder sourceBuilder) {
        if(req.getPriceSortType() != null){
            //销量升序
            if(req.getPriceSortType().equals(LocalFunSortType.ASC.getCode())){
                sourceBuilder.sort(req.getChannelId().longValue() == ChannelMainData.APP_H5.getId()? SortBuilders.fieldSort("h5Price").order(SortOrder.ASC) : SortBuilders.fieldSort("pcPrice").order(SortOrder.ASC));
            }
            //销量倒序
            if(req.getPriceSortType().equals(LocalFunSortType.DESC.getCode())){
                sourceBuilder.sort(req.getChannelId().longValue() == ChannelMainData.APP_H5.getId()? SortBuilders.fieldSort("h5Price").order(SortOrder.DESC) : SortBuilders.fieldSort("pcPrice").order(SortOrder.DESC));
            }
        }
    }

    private void querySold(PCLocalFunQueryReq req, SearchSourceBuilder sourceBuilder) {
        if(req.getSoldSortType() != null){
            //销量升序
            if(req.getSoldSortType().equals(LocalFunSortType.ASC.getCode())){

                sourceBuilder.sort(SortBuilders.fieldSort("saleCount").order(SortOrder.ASC));
            }
            //销量倒序
            if(req.getSoldSortType().equals(LocalFunSortType.DESC.getCode())){
                sourceBuilder.sort(SortBuilders.fieldSort("saleCount").order(SortOrder.DESC));
            }
        }
    }

    /**
     * 排序方式
     *
     * @param sort
     * @return
     */
    private SortOrder getSorted(String sort) {
        return sort == null ? SortOrder.DESC : sort.equals("asc") ? SortOrder.ASC : SortOrder.DESC;
    }

    /**
     * 检索缓存初始化
     *
     * @return
     */
    @Override
    public Result cacheInit() {
        LocalDateTime now = LocalDateTime.now();
        // 检索缓存上架es
        List<LocalFunProductCacheUpDTO> cache = buildLocalProductInfo(null);
        upElasticSearch(cache,false);
        upRedis(cache);
        // 标记缓存更新时间
        setUpdateTime(RedisConstants.LOCAL_FUN_UPDATE_TIME, now);
        return Result.success(cache);
    }

    /**
     * 查询产品详情
     * 如果产品详情redis中有，从redis拿，如果没有，从数据库查
     * 数据库中有，上架redis并返回，如果没有，返回错误信息
     *
     * @param req
     * @return
     */
    @Override
    public Result<LocalFunProductDetailsResDTO> queryProductDetails(LocalFunProductDetailsQueryReq req) {
        Long productId = req.getProductId();
        String obj = redisTemplate.opsForValue().get(RedisConstants.LOCAL_FUN_PRODUCT_DETAILS + productId);
        if (StringUtil.isNotEmpty(obj)) {
            LocalFunProductDetailsCacheDTO productDetailsCache = JSON.parseObject(obj, LocalFunProductDetailsCacheDTO.class);
            LocalFunProductDetailsResDTO result = EntityUtil.copy(productDetailsCache, LocalFunProductDetailsResDTO.class);
            handleJson(productDetailsCache,result);
            result.setCancelPolicy(productDetailsCache.getCancelPolicy() == null ? "" : (productDetailsCache.getCancelPolicy() == 1 ?"限時取消":"不可退訂"));
            result.setTitle(productDetailsCache.getProductName());
            result.setAttrs(req.getChannelId().longValue() == ChannelMainData.CHINA_TRAVEL.getId()?productDetailsCache.getPcAttrs():productDetailsCache.getH5Attrs());

            handleAttr(result);
            // 增加足迹
            addUserFootPoint(result,req.getProductId(),req.getUserId());
            return Result.success(result);
        }

        List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = buildLocalProductInfo(new LocalFunProductCacheOpsReq().setIds(CollectionUtil.toList(productId)));
        if (localFunProductCacheUpDTOS == null || localFunProductCacheUpDTOS.size() <= 0) {
            return Result.failed(SystemError.LOCAL_FUN_18015);
        }

        LocalFunProductDetailsCacheDTO copy = EntityUtil.copy(localFunProductCacheUpDTOS.get(0), LocalFunProductDetailsCacheDTO.class);
        LocalFunProductDetailsResDTO result = EntityUtil.copy(copy, LocalFunProductDetailsResDTO.class);
        handleJson(copy,result);
        result.setCancelPolicy(copy.getCancelPolicy() == 1 ?"限時取消":"不可退訂");
        result.setTitle(copy.getProductName());
        result.setAttrs(req.getChannelId().longValue() == ChannelMainData.CHINA_TRAVEL.getId()?copy.getPcAttrs():copy.getH5Attrs());
        upRedis(localFunProductCacheUpDTOS);

        handleAttr(result);
        // 增加足迹
        addUserFootPoint(result,req.getProductId(),req.getUserId());
        return Result.success(result);
    }

    private void handleJson(LocalFunProductDetailsCacheDTO productDetailsCache, LocalFunProductDetailsResDTO result) {
        List<FutureJson> futureJson = productDetailsCache.getFutureJson();
        if(CollectionUtil.isNotEmpty(futureJson)){
            List<String> futrueList = futureJson.stream().map(FutureJson::getFuture).collect(Collectors.toList());
            result.setFutureJson(futrueList);
        }
        List<PhotosJson> photoJson = productDetailsCache.getPhotosJson();
        if(CollectionUtil.isNotEmpty(photoJson)){
            List<String> photoList = photoJson.stream().map(PhotosJson::getUrl).collect(Collectors.toList());
            result.setPhotosJson(photoList);
            result.setCoverImage(photoList == null ? "" :photoList.get(0));
        }
        List<ThemeJson> themeJson = productDetailsCache.getThemeJson();
        if(CollectionUtil.isNotEmpty(themeJson)){
            List<String> themeList = themeJson.stream().map(ThemeJson::getTheme).collect(Collectors.toList());
            result.setThemeJson(themeList);
        }
    }

    private void handleAttr(LocalFunProductDetailsResDTO result) {
        List<LocalFunAttrParentDto> list = Lists.newArrayListWithExpectedSize(10);
        List<LocalFunAttrDTO> attrs = result.getAttrs();
        if(CollectionUtil.isNotEmpty(attrs)){
            Map<Long, List<LocalFunAttrDTO>> listMap = attrs.stream().collect(Collectors.groupingBy(LocalFunAttrDTO::getAttr1_id));
            listMap.forEach((k,v)->{
                LocalFunAttrDTO localFunAttrDTO = v.stream().findFirst().orElse(null);
                LocalFunAttrParentDto dto = new LocalFunAttrParentDto();
                dto.setAttrDTOList(v);
                dto.setAttrId1(k);
                if(localFunAttrDTO != null){
                    dto.setAttrName1(localFunAttrDTO.getAttr1Name());
                }
                list.add(dto);
            });
        }
        result.setList(list);
    }

    @Override
    public BigDecimal queryProducFee(Long id, BigDecimal price) {
        LocalFunProduct localFunProduct = baseMapper.selectOne(Wrappers.<LocalFunProduct>lambdaQuery().eq(LocalFunProduct::getId, id));
        BigDecimal reserveCharge = localFunProduct.getReserveCharge();
        Integer reserveChargeType = localFunProduct.getReserveChargeType();
        if (reserveCharge == null) {
            return BigDecimal.ZERO;
        }
        if (reserveChargeType == 1) {
            return reserveCharge.add(price);
        }
        if (reserveChargeType == 2) {
            return price.add(price.multiply(reserveCharge));
        }
        return BigDecimal.ZERO;
    }

    /**
     * 缓存更新
     *
     * @return
     */
    @Override
    public Result cacheUpdate(LocalFunProductCacheOpsReq req) {
        log.info("当地玩乐缓存更新入参：{}",JSON.toJSONString(req));
        List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = buildLocalProductInfo(req);
        upElasticSearch(localFunProductCacheUpDTOS,true);
        upRedis(localFunProductCacheUpDTOS);
        log.info("缓存更新成功：{}",JSON.toJSONString(localFunProductCacheUpDTOS));
        return Result.success();
    }

    @Override
    public Result deleteCache(LocalFunProductCacheOpsReq req) {
        req.getIds().stream().forEach(id->{
            localFunElasticSearchMapper.deleteById(id);
        });
        return Result.success();
    }

    /**
     * 剩余票数操作
     *
     * @return
     */
    @Override
    public Result remainderNumberOps(RemainderNumOpsReq req) {
        String key = RedisConstants.LOCAL_FUN_REMAINDER_NUMBER + req.getKey();
        Boolean hasKey = redisTemplate.hasKey(key);
        // 如果key不存在
        if (!hasKey) {
            return Result.failed(SystemError.LOCAL_FUN_18015);
        }
        // 绑定操作key
        BoundValueOperations<String, String> valueOps = redisTemplate.boundValueOps(key);
        Integer type = req.getType();
        String msg = "操作成功";
        RLock lock = redissonClient.getLock(RedisConstants.LOCAL_FUN_REMAINDER_NUMBER_LOCK + req.getKey());
        try {
            lock.lock(30, TimeUnit.SECONDS);
            Integer num = Integer.valueOf(valueOps.get());
            // 根据操作类型执行操作
            if (RedisOpsStatusCode.ADD.getCode().intValue() == type.intValue()) {
                if (num != -1) {
                    Long increment = valueOps.increment(req.getVal());
                    msg = " 剩余票数为：" + increment;
                }
            } else if (RedisOpsStatusCode.MIN.getCode().intValue() == type.intValue()) {
                if (num - req.getVal() >= 0) {
                    Long decrement = valueOps.decrement(req.getVal());
                    msg = " 剩余票数为：" + decrement;
                } else {
                    return Result.failed(SystemError.LOCAL_FUN_18016);
                }
            } else if (RedisOpsStatusCode.QUERY.getCode().intValue() == type.intValue()) {
                return Result.success(Long.valueOf(valueOps.get().toString()));
            } else if (RedisOpsStatusCode.SET.getCode().intValue() == type.intValue()) {
                valueOps.set(req.getVal().toString());
            } else if (RedisOpsStatusCode.DEL.getCode().intValue() == type.intValue()) {
                Map<String, Boolean> resultMap = deletString(Arrays.asList(key));
                Set<String> keySet = resultMap.keySet();
                Iterator<String> iterator = keySet.iterator();
                Boolean result = resultMap.get(iterator.next());
                if (!result) {
                    msg = "操作失败";
                }
            }
        } finally {
            lock.unlock();
        }
        return Result.success(msg);
    }

    /**
     * 当地玩乐 - 订单支付详情列表
     *
     * @param queryNumber
     * @return
     */
    @Override
    public Result payDetails(String queryNumber, TokenUser user) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 使用订单查询号或用户id检索订单支付详情
        if (!StringUtils.isEmpty(queryNumber)) {
            boolQuery.must(QueryBuilders.matchPhraseQuery("queryNumber", queryNumber));
        } else {
            boolQuery.must(QueryBuilders.matchPhraseQuery("userId", user != null ? user.getId() : Result.failed(SystemError.LOCAL_FUN_18019)));
        }
        sourceBuilder.query(boolQuery);
        // 排序
        sourceBuilder.sort("gmtModified", SortOrder.DESC);

        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_PAY_DETAILS).source(sourceBuilder);

        // 解析检索响应结果
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);

            // 不存在检索记录
            SearchHit[] hits = response.getHits().getHits();
            if (hits.length <= 0) return Result.success(null);

            // 封装检索结果
            List<LocalFunPayDetailsCacheDTO> localFunPayDetailsCacheDTOS = new ArrayList<>();
            for (SearchHit hit : hits) {
                String str = hit.getSourceAsString();
                LocalFunPayDetailsCacheDTO localFunPayDetailsCacheDTO = JSON.parseObject(str, LocalFunPayDetailsCacheDTO.class);
                localFunPayDetailsCacheDTOS.add(localFunPayDetailsCacheDTO);
            }
            return Result.success(localFunPayDetailsCacheDTOS);
        } catch (IOException e) {
            log.error("【业务模块APP服務-查询es产品列表】异常", e);
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        } finally {
            try {
                restHighLevelClient.close();
            } catch (IOException e) {
                throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
            }
        }
    }

    @Override
    public Result<LocalFunPriceDTO> getPrice(LocalFunPriceReq req) {
        LocalFunPriceDTO localFunPriceDTO = localFunProductMapper.queryPriceById(req);
        return Result.success(localFunPriceDTO);
    }

    @Override
    public Result<LocalFunReservationGetOrderDTO> getOrderInfo(LocalFunOrderReq localFunOrderReq, Long currentUserAllowNotLogInUserId) {
        log.info("入参：{}",JSON.toJSONString(localFunOrderReq));
        LocalFunProduct localFunProduct = localFunProductMapper.selectById(localFunOrderReq.getProductId());
        if(null == localFunProduct){
            throw new BusinessException(SystemError.SYS_411);
        }
        LocalFunReservationGetOrderDTO dto = EntityUtil.copy(localFunProduct, LocalFunReservationGetOrderDTO.class);
        //封面图地址
        List<PhotosJson> photosJsons = Objects.isNull(localFunProduct.getPhotosJson()) ? Lists.newArrayListWithCapacity(1) : JSON.parseArray(localFunProduct.getPhotosJson(), PhotosJson.class);
        if(CollectionUtil.isNotEmpty(photosJsons)){
            PhotosJson photosJson1 = photosJsons.get(0);
            dto.setCoverImage(photosJson1 != null ? photosJson1.getUrl() : "");
        }
        try{
            LocalFunPriceRemainderDTO localFunSalePriceInfoHandle = findLocalFunSalePriceInfoHandle(3, localFunOrderReq.getProductId(), LocalDate.now(), localFunOrderReq.getChannelId(), localFunOrderReq.getAttrId1(), localFunOrderReq.getAttrId2());
            log.info("价格信息：{}",JSON.toJSONString(localFunSalePriceInfoHandle));

            if(localFunSalePriceInfoHandle != null){
                dto.setReserveCharge(Objects.isNull(localFunSalePriceInfoHandle.getReserveCharge())?null:NumberUtil.getIntegerAmount(localFunSalePriceInfoHandle.getReserveCharge()));
                dto.setCostPrice(NumberUtil.getIntegerAmount(localFunSalePriceInfoHandle.getCostPrice()));
                dto.setExchangeRate(localFunSalePriceInfoHandle.getExchangeRate());
                dto.setPrice(NumberUtil.getIntegerAmount(localFunSalePriceInfoHandle.getPrice()));
                dto.setSolidNumber(localFunSalePriceInfoHandle.getSolidNumber());
                dto.setAttr1Name(localFunSalePriceInfoHandle.getAttr1Name());
                dto.setAttr2Name(localFunSalePriceInfoHandle.getAttr2Name());
            }
        } catch (Exception e) {
            return Result.failed(SystemError.LOCAL_FUN_18026);
        }

        //证件类型
        String certificateType = localFunProduct.getCertificateType();
        if(StringUtil.isNotEmpty(certificateType)){
            String[] split = certificateType.split(",");
            dto.setCertificateTypeList(Arrays.asList(split));
        }

        if(localFunProduct.getVaildType() == 1){
            dto.setUseDay(localFunProduct.getVaildDay().intValue());
        }else {
            //有效天数
            if(localFunProduct.getVaildDayBegin() != null && localFunProduct.getVaildDayEnd() != null){
                int daysBetween = DateUtil.daysBetween(localFunProduct.getVaildDayEnd(), localFunProduct.getVaildDayBegin());
                dto.setUseDay(daysBetween);
            }else {
                dto.setUseDay(0);
            }
        }

        //计算获取积分
        try {
            BigDecimal amount = dto.getPrice().add(dto.getReserveCharge()).multiply(new BigDecimal(localFunOrderReq.getTicketNum()));
            AppUserDTO userDTO = userService.queryCacheUser(currentUserAllowNotLogInUserId);
            int integral = CalcIntegralUtil.getObtainByType(SystemBusinessType.LOCAL_FUN.getCode(), userDTO.getMemberLevel(), amount);
            dto.setIntegral(integral);
        } catch (Exception e) {
            dto.setIntegral(0);
            log.error("获取积分信息失败");
        }
        return Result.success(dto);
    }

    @Override
    public Result<LocalFunSearchDTO> queryKindProduct(PCLocalFunQueryReq req) {
        //当地玩乐
        LocalFunSearchDTO cache = null;
        try {
            cache = getLocalFunSearch(req);
        } catch (Exception e) {
            log.error("[当地玩乐首页]-首页当地玩乐列表检索错误：{}",e.getMessage());
        }
        return Result.success(cache);
    }

    /**
     * PC当地玩乐-搜索查询
     * @param req
     * @return
     */
    private LocalFunSearchDTO getLocalFunSearch(PCLocalFunQueryReq req) {
        LocalFunSearchDTO localFunSearchDTO = new LocalFunSearchDTO();
        List<FunTypeLabel> funTypeList = Lists.newArrayListWithExpectedSize(10);
        // 处理响应结果
        try {
            //处理标签页
            handleLabel(req, funTypeList);

            SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_INDEX).source(buildSearchQuery(req,funTypeList));
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHits hits = response.getHits();
            List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = new ArrayList<>();
            // 反序列化命中记录
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                LocalFunProductCacheUpDTO localFunProductCacheUpDTO = JSON.parseObject(sourceAsString, LocalFunProductCacheUpDTO.class);
                localFunProductCacheUpDTOS.add(localFunProductCacheUpDTO);
            }
            List<LocalFunProductResDTO> localFunProductResDTOS = EntityUtil.copyList(localFunProductCacheUpDTOS, LocalFunProductResDTO.class);
            localFunProductResDTOS.forEach(res -> {
                localFunProductCacheUpDTOS.forEach(cache -> {
                    if (res.getId().equals(cache.getId())) {
                        res.setTitle(cache.getProductName());
                        res.setCoverImage(Objects.isNull(cache.getPhotosJson())?"":getUrl(cache));
                        res.setPrice(ChannelMainData.CHINA_TRAVEL.getId() == req.getChannelId().longValue() ? NumberUtil.getIntegerAmount(new BigDecimal(cache.getPcPrice())) : NumberUtil.getIntegerAmount(new BigDecimal(cache.getH5Price())));
                        res.setThemeList(getThemeList(cache.getThemeJson()));
                    }
                });
            });
            ParsedCardinality total = response.getAggregations().get("product_total");
            localFunSearchDTO.setTotal((int)total.getValue());
            localFunSearchDTO.setLabels(funTypeList);
            localFunSearchDTO.setPageNo(req.getPageNo());
            localFunSearchDTO.setPageSize(req.getPageNum());
            localFunProductResDTOS = localFunProductResDTOS.stream().filter(entity->entity.getPrice()!=null && entity.getPrice()!=BigDecimal.ZERO).collect(Collectors.toList());
            localFunSearchDTO.setLocalFunProductResDTOS(localFunProductResDTOS);
        } catch (IOException e) {
            log.error("【业务模块APP服務-查询es产品列表】异常", e);
            throw new BusinessException(SystemError.ES_IO_EXCEPTION_19004);
        }
        return localFunSearchDTO;
    }

    /**
     * 处理标签页
     * @param req
     * @param funTypeList
     * @throws IOException
     */
    private void handleLabel(PCLocalFunQueryReq req, List<FunTypeLabel> funTypeList) throws IOException {
        //城市搜索标签页
        if(StringUtil.isEmpty(req.getKey())){
            SearchResponse globalResponse = restHighLevelClient.search(aggsRequestSearch(req,funTypeList), ElasticSearchConfiguration.COMMON_OPTIONS);
            Aggregations aggregations = globalResponse.getAggregations();
            ParsedStringTerms type_id_aggs = aggregations.get("type_id_agg");
            if (type_id_aggs != null && type_id_aggs.getBuckets().size() > 0) {
                // 遍历游玩类型id聚合
                for (Terms.Bucket bucket : type_id_aggs.getBuckets()) {
                    // 游玩类型id
                    long funType = bucket.getKeyAsNumber().longValue();
                    ParsedStringTerms aggregation = bucket.getAggregations().get("type_name_agg");
                    String funName = aggregation.getBuckets().get(0).getKeyAsString();
                    FunTypeLabel label = new FunTypeLabel();
                    label.setFunType(funType);
                    label.setFunName(funName);
                    funTypeList.add(label);
                }
            }
        }
        //条件key+城市搜索标签页
        else {
            SearchResponse globalResponse = restHighLevelClient.search(aggsKeyRequestSearch(req), ElasticSearchConfiguration.COMMON_OPTIONS);
            Aggregations aggregations = globalResponse.getAggregations();
            ParsedStringTerms type_id_aggs = aggregations.get("type_id_agg");
            if (type_id_aggs != null && type_id_aggs.getBuckets().size() > 0) {
                // 遍历游玩类型id聚合
                for (Terms.Bucket bucket : type_id_aggs.getBuckets()) {
                    // 游玩类型id
                    long funType = bucket.getKeyAsNumber().longValue();
                    ParsedStringTerms aggregation = bucket.getAggregations().get("type_name_agg");
                    String funName = aggregation.getBuckets().get(0).getKeyAsString();
                    FunTypeLabel label = new FunTypeLabel();
                    label.setFunType(funType);
                    label.setFunName(funName);
                    funTypeList.add(label);
                }
            }
        }
    }

    /**
     * 搜索key+城市的标签页
     * @param req
     * @return
     */
    private SearchRequest aggsKeyRequestSearch(PCLocalFunQueryReq req) {
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_INDEX);

        if (req == null) throw new BusinessException(SystemError.LOCAL_FUN_18020);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 城市id
        if (req.getCityId() != null && req.getCityId() != 0) {
            boolQuery.filter(QueryBuilders.termQuery("cityId", req.getCityId()));
        }
        // 城市名称
        if (req.getCityName() != null) {
            boolQuery.filter(QueryBuilders.termQuery("cityName",req.getCityName()));
        }
        // 前端输入的检索条件
        if (!StringUtils.isEmpty(req.getKey())) {
            boolQuery.must(QueryBuilders.boolQuery().should(QueryBuilders.matchPhraseQuery("productName",req.getKey()))
                    .should(QueryBuilders.matchPhraseQuery("funName",req.getKey())));
        }
        boolQuery.mustNot(QueryBuilders.termQuery("pcPrice",BigDecimal.ZERO));
        sourceBuilder.query(boolQuery);
        // 聚合游玩类型
        TermsAggregationBuilder type_id_agg = AggregationBuilders.terms("type_id_agg").field("funType");
        type_id_agg.subAggregation(AggregationBuilders.terms("type_name_agg").field("funName"));

        sourceBuilder.aggregation(type_id_agg);
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }

    /**
     * 全局聚合 展示城市全部标签
     * @return
     * @param req
     * @param funTypeList
     */
    private SearchRequest aggsRequestSearch(PCLocalFunQueryReq req, List<FunTypeLabel> funTypeList) {
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_INDEX);

        if (req == null) throw new BusinessException(SystemError.LOCAL_FUN_18020);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 城市id
        if (req.getCityId() != null && req.getCityId() != 0) {
            boolQuery.filter(QueryBuilders.termQuery("cityId", req.getCityId()));
        }
        // 城市名称
        if (req.getCityName() != null) {
            boolQuery.filter(QueryBuilders.termQuery("cityName",req.getCityName()));
        }
        if (CollectionUtils.isNotEmpty(funTypeList)) {
            boolQuery.filter(QueryBuilders.termQuery("funType",funTypeList.get(0).getFunType()));
        }
        boolQuery.mustNot(QueryBuilders.termQuery("pcPrice",BigDecimal.ZERO));
        sourceBuilder.query(boolQuery);
        // 聚合游玩类型
        TermsAggregationBuilder type_id_agg = AggregationBuilders.terms("type_id_agg").field("funType");
        type_id_agg.subAggregation(AggregationBuilders.terms("type_name_agg").field("funName"));

        sourceBuilder.aggregation(type_id_agg);
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }

    /**
     * 主题列表
     * @param themeJson
     * @return
     */
    private List<String> getThemeList(List<ThemeJson> themeJson) {
        List<String> themeList = Lists.newArrayListWithExpectedSize(10);
        if(CollectionUtil.isNotEmpty(themeJson)){
            themeJson.stream().forEach(entity->{
                themeList.add(entity.getTheme());
            });
        }
        return themeList;
    }

    /**
     * 搜索类别-查询
     * @param req
     * @param funTypeList
     * @return
     */
    private SearchSourceBuilder buildSearchQuery(PCLocalFunQueryReq req, List<FunTypeLabel> funTypeList) {
        // 入参为空
        if (req == null) throw new BusinessException(SystemError.LOCAL_FUN_18020);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 前端输入的检索条件
        if (!StringUtils.isEmpty(req.getKey())) {
            boolQuery.must(QueryBuilders.boolQuery().should(QueryBuilders.matchPhraseQuery("productName",req.getKey()))
                    .should(QueryBuilders.matchPhraseQuery("funName",req.getKey())));
        }
        //当地玩乐类型
        if (!StringUtils.isEmpty(req.getFunType())) {
            boolQuery.filter(QueryBuilders.termQuery("funType", req.getFunType()));
        }
        if(StringUtil.isEmpty(req.getKey()) && req.getFunType() == null && CollectionUtils.isNotEmpty(funTypeList)){
            boolQuery.filter(QueryBuilders.termQuery("funType", funTypeList.get(0).getFunType()));
        }
        // 城市id
        if (req.getCityId() != null && req.getCityId() != 0) {
            boolQuery.filter(QueryBuilders.termQuery("cityId", req.getCityId()));
        }
        // 城市名称
        if (req.getCityName() != null) {
            boolQuery.filter(QueryBuilders.matchQuery("cityName",req.getCityName()));
        }
        //搜索主题名称
        if(StringUtil.isNotEmpty(req.getThemeName())){
            String[] themes = req.getThemeName().split(",");
            Arrays.stream(themes).forEach(theme->{
                boolQuery.must(QueryBuilders.nestedQuery("themeJson", QueryBuilders.matchQuery("themeJson.theme.keyword",theme), ScoreMode.None));
            });
        }
        boolQuery.mustNot(QueryBuilders.termQuery("pcPrice",BigDecimal.ZERO));
        sourceBuilder.query(boolQuery);
        // 聚合游玩类型
        TermsAggregationBuilder type_id_agg = AggregationBuilders.terms("type_id_agg").field("funType");
        type_id_agg.subAggregation(AggregationBuilders.terms("type_name_agg").field("funName"));

        CardinalityAggregationBuilder total = AggregationBuilders.cardinality("product_total").field("id");
        sourceBuilder.aggregation(type_id_agg);
        sourceBuilder.aggregation(total);

        // 分页
        sourceBuilder.from(req.getPageNo() == null || req.getPageNo() == 0 ? 0 : req.getPageNo() - 1 * req.getPageNum());
        sourceBuilder.size(req.getPageNum() == null || req.getPageNum() == 0? ElasticSearchConstant.PAGE_SIZE : req.getPageNum());

        // 排序
        if (req.getSoldSortType() != null  || req.getPriceSortType() != null){
            if(req.getSort() != null){
                if(LocalFunSortType.BEFORE.getCode().equals(req.getSort())){
                    queryPrice(req, sourceBuilder);
                    querySold(req, sourceBuilder);
                }else {
                    querySold(req, sourceBuilder);
                    queryPrice(req, sourceBuilder);
                }
            }else {
                queryPrice(req, sourceBuilder);
                querySold(req, sourceBuilder);
            }
        } else {
            sourceBuilder.sort(new FieldSortBuilder("isRecommend").order(SortOrder.DESC));
            sourceBuilder.sort(new FieldSortBuilder("saleCount").order(SortOrder.DESC));
            sourceBuilder.sort(new FieldSortBuilder("createDate").order(SortOrder.DESC));
        }
        return sourceBuilder;
    }

    @Override
    public Result<List<LocalFunHomeTitleDTo>> getHomeTitleList(PCLocalFunQueryReq req) {
        return Result.success(getLocalHomeTitle(req));
    }

    @Override
    public boolean noMoreSurplusNumber(Long productId,Long attrId1, Long attrId2,Integer strategyType,Long relId) {
        LocalFunCandidateProductDto dto = null;
        switch (strategyType){
            case 1:
                List<LocalFunCandidateProductDto> list = localFunProductSaleStrategyMapper.selectCandidatePrivliage(productId,attrId1,attrId2,LocalDate.now(),null);
                //构建加辐信息
                dto = buildCandidateDto(relId, dto, list,strategyType);
                break;
            case 2:
                List<LocalFunCandidateProductDto> agentList  = localFunProductSaleStrategyMapper.selectCandidateAgentPrivliage(productId,attrId1,attrId2,LocalDate.now(),null);
                //构建加辐信息
                dto = buildCandidateDto(relId, dto, agentList,strategyType);
                break;
            case 3:
                dto = localFunProductSaleStrategyMapper.selectCandidateOfficePrivliage(productId,attrId1,attrId2,LocalDate.now(),relId);
                break;
            case 4:
                dto = localFunProductSaleStrategyMapper.selectCandidateThirdPrivliage(productId,attrId1,attrId2,LocalDate.now(),relId);
                break;
            default:
                break;
        }

        log.info("查询余票信息：{}",JSON.toJSONString(dto));

        if(dto == null){
            return true;
        }
        Integer solidNumber = getSolidNumber(dto);
        return  solidNumber == null || solidNumber.intValue() <= 0 ? true : false;
    }

    protected LocalFunCandidateProductDto buildCandidateDto(Long relId, LocalFunCandidateProductDto dto, List<LocalFunCandidateProductDto> list, Integer strategyType) {
        if(CollectionUtils.isNotEmpty(list)){
            //自营
            if(strategyType.intValue() == 1){
                for(LocalFunCandidateProductDto dto1 : list){
                    if(dto == null && dto1 != null){
                        //全部门可售
                        if(dto1.getVisableType() == 2){
                            dto = dto1;
                        }
                        //指定人员或部门可售
                        if(dto1.getVisableType() == 3){
                            //部门对应的加辐
                            if(dto1.getCheckType() == 2){
                                UserDTO byUserId = iSysUserService.getByUserId(relId);
                                dto = list.stream().filter(entity -> byUserId.getDeptId().equals(entity.getRelationId())).findFirst().orElse(null);
                            }
                            //人员对应的加辐
                            if(dto1.getCheckType() == 1){
                                dto = list.stream().filter(entity -> relId.equals(entity.getRelationId())).findFirst().orElse(null);
                            }
                        }
                    }
                }
            }
            //代理商
            else {
                LocalFunCandidateProductDto dto1 = list.stream().findFirst().orElse(null);
                if(dto1 != null){
                    if(dto1.getVisableType() == 2){
                     dto = dto1;
                    }
                    if(dto1.getVisableType() == 3){
                        dto = list.stream().filter(entity->relId.equals(entity.getRelationId())).findFirst().orElse(null);
                    }
                }
            }
        }
        return dto;
    }


    @Override
    public LocalFunStockInfo getCostTouristStockInfo(Long attrId1, Long attrId2) {
        QueryWrapper<LocalFunProductCostStrategySpecStock> queryWrapper = new QueryWrapper<>();
        if(attrId1 != null){
            queryWrapper.lambda().eq(LocalFunProductCostStrategySpecStock::getAttr1Id,attrId1);
        }
        if(attrId2 != null){
            queryWrapper.lambda().eq(LocalFunProductCostStrategySpecStock::getAttr2Id,attrId2);
        }
        LocalFunProductCostStrategySpecStock specStock = localFunProductCostStrategySpecStockMapper.selectOne(queryWrapper);
        if(specStock == null){
            return null;
        }
        return LocalFunStockInfo.builder().attrName1(specStock.getAttr1Name()).costPrice(specStock.getCostPrice())
                .attrName2(specStock.getAttr2Name()).build();
    }

    @Override
    public Result<List<HotLocalFunDto>> getHotRecommends(PCLocalFunQueryReq req) {
        List<HotLocalFunDto> list = Lists.newArrayListWithExpectedSize(10);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        sourceBuilder.sort(new FieldSortBuilder("isRecommend").order(SortOrder.DESC));
        boolQuery.mustNot(QueryBuilders.termQuery("pcPrice",BigDecimal.ZERO));
        sourceBuilder.query(boolQuery);
        sourceBuilder.from(req.getPageNo());
        sourceBuilder.size(req.getPageNum());
        SearchRequest searchRequest = new SearchRequest(ElasticSearchConstant.LOCAL_FUN_INDEX).source(sourceBuilder);
        // 处理响应结果
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticSearchConfiguration.COMMON_OPTIONS);
            SearchHits hits = response.getHits();
            List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = new ArrayList<>();
            // 反序列化命中记录
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                LocalFunProductCacheUpDTO localFunProductCacheUpDTO = JSON.parseObject(sourceAsString, LocalFunProductCacheUpDTO.class);
                localFunProductCacheUpDTOS.add(localFunProductCacheUpDTO);
            }
            if(CollectionUtils.isNotEmpty(localFunProductCacheUpDTOS)){
                localFunProductCacheUpDTOS.stream().forEach(entity->{
                    HotLocalFunDto dto = HotLocalFunDto.builder().cityName(entity.getCityName())
                            .price(ChannelMainData.CHINA_TRAVEL.getId() == req.getChannelId().longValue() ? NumberUtil.getIntegerAmount(new BigDecimal(entity.getPcPrice())):NumberUtil.getIntegerAmount(new BigDecimal(entity.getH5Price())))
                            .funName(entity.getFunName())
                            .coverImage(Objects.isNull(entity.getPhotosJson())?"":getUrl(entity))
                            .title(entity.getProductName())
                            .isRecommend(entity.getIsRecommend())
                            .productId(entity.getId())
                            .build();
                    list.add(dto);
                });
            }
        }catch (Exception e){
            log.error("ES");
        }
        return Result.success(list);
    }

    private List<LocalFunHomeTitleDTo> getLocalHomeTitle(PCLocalFunQueryReq req) {
        String obj = redisTemplate.opsForValue().get(String.format(HOME_TITLE_KEY, req.getChannelId(), req.getCityId()));
        if(StringUtil.isNotEmpty(obj)){
            return JSON.parseArray(obj,LocalFunHomeTitleDTo.class);
        }
        List<LocalFunHomeTitleDTo> list = Lists.newArrayListWithExpectedSize(10);
        //获取当地玩乐产品信息
        list.addAll(addLocalFunHomeTitleDTos(req));
        redisTemplate.opsForValue().set(String.format(HOME_TITLE_KEY, req.getChannelId(), req.getCityId()),JSON.toJSONString(list),1,TimeUnit.MINUTES);
        return list;
    }

    /**
     * 检索缓存上架es
     *
     * @param localFunProductCacheUpDTOS
     * @param update
     * @return
     */
    private Result upElasticSearch(List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS, boolean update) {
        if(update){
            localFunProductCacheUpDTOS.stream().forEach(entity->{
                localFunElasticSearchMapper.deleteById(entity.getId());
            });
        }else {
            localFunElasticSearchMapper.deleteAll();
        }
        List<LocalFunProductCacheUp> localFunProductCacheUps = EntityUtil.copyList(localFunProductCacheUpDTOS, LocalFunProductCacheUp.class);
        localFunElasticSearchMapper.saveAll(localFunProductCacheUps);
        return Result.success();
    }

    /**
     * 产品详情上架redis
     *
     * @param localFunProductCacheUpDTOS
     * @return
     */
    private Result upRedis(List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS) {
        // 产品详情上架redis
        List<LocalFunProductDetailsCacheDTO> localFunProductDetailsCacheDTOS = EntityUtil.copyList(localFunProductCacheUpDTOS, LocalFunProductDetailsCacheDTO.class);
        for(LocalFunProductDetailsCacheDTO dto : localFunProductDetailsCacheDTOS){
            redisTemplate.opsForValue().set(RedisConstants.LOCAL_FUN_PRODUCT_DETAILS+dto.getId(),JSON.toJSONString(dto));
        }
        return Result.success();
    }


    /**
     * 构建缓存产品信息
     * gzl-wanis001
     * @return
     */
    private List<LocalFunProductCacheUpDTO> buildLocalProductInfo(LocalFunProductCacheOpsReq req){
        List<LocalFunProductCacheUpDTO> cacheList = Lists.newArrayListWithExpectedSize(10);
        /**
         * 查询符合条件的产品信息
         * 1.上架
         * 2.当前在-售卖日期之内
         */
        List<LocalFunCandidateProductDto> candidateProductList = localFunProductMapper.queryCandidateProduct(LocalDate.now(),req);

        if(CollectionUtil.isEmpty(candidateProductList)){
            log.info("[当地玩乐-缓存初始化]--未查询到符合条件的产品");
        }
        //产品ID分组，获取产品各个规格信息
        Map<Long, List<LocalFunCandidateProductDto>> productMap = candidateProductList.stream().collect(Collectors.groupingBy(
                LocalFunCandidateProductDto::getProductId));

        productMap.forEach((productId,productAttrList)->{
            //拿产品基本信息
            LocalFunCandidateProductDto entity = productAttrList.stream().findFirst().orElse(null);
            if(entity != null){
                List<LocalFunAttrDTO> h5Attrs = getH5Attrs(productAttrList);
                List<LocalFunAttrDTO> pcAttrs = getPcAttrs(productAttrList);
                cacheList.add(LocalFunProductCacheUpDTO.builder()
                        .cityId(entity.getCityId()).cityName(entity.getCityName()).coverImage(entity.getCoverImage())
                        .funName(entity.getFunName()).createDate(entity.getCreateDate()).funType(entity.getFunType())
                        .futureJson(Objects.isNull(entity.getFutureJson())? Lists.newArrayListWithCapacity(1):JSON.parseArray(entity.getFutureJson(),FutureJson.class))
                        .id(entity.getProductId()).isRecommend(entity.getIsRecommend()).photosJson(Objects.isNull(entity.getPhotosJson())? Lists.newArrayListWithCapacity(1):JSON.parseArray(entity.getPhotosJson(),PhotosJson.class))
                        .themeJson(getTheme(entity)).productCode(entity.getProductCode())
                        .productIntroduces(entity.getProductIntroduces())
                        .title(entity.getThemeJson())
                        .h5Attrs(h5Attrs)
                        .productName(entity.getProductName())
                        .pcAttrs(pcAttrs)
                        .h5Price(getMinPrice(h5Attrs,pcAttrs,true).doubleValue())
                        .cancelPolicy(entity.getCancelPolicy())
                        .customerNotice(entity.getCustomerNotice())
                        .useInstructions(entity.getUseInstructions())
                        .pcPrice(getMinPrice(h5Attrs,pcAttrs,false).doubleValue())
                        .build());
            }
        });
        log.info("[当地玩乐-缓存初始化]--完成对符合条件的所有产品的构建-准备缓存");
        return cacheList;
    }

    private List<LocalFunAttrDTO> getOriginAttrs(List<LocalFunAttrDTO> productAttrList,List<LocalFunAttrDTO> h5Attrs) {
        if(CollectionUtils.isEmpty(productAttrList) && CollectionUtils.isEmpty(h5Attrs)){
            return Lists.newArrayListWithExpectedSize(1);
        }
        if(CollectionUtils.isEmpty(productAttrList)){
            return h5Attrs.stream().map(entity-> LocalFunAttrDTO.builder()
                    .price(NumberUtil.getIntegerAmount(entity.getCostPrice().multiply(entity.getExchangeRate()).add(entity.getReserveCharge())))
                    .attr2Name(entity.getAttr2Name()).attr1Name(entity.getAttr1Name())
                    .surplusStock(entity.getSurplusStock())
                    .attr1_id(entity.getAttr1_id()).attr2_id(entity.getAttr2_id())
                    .id(entity.getId())
                    .costPrice(NumberUtil.getIntegerAmount(entity.getCostPrice()))
                    .exchangeRate(entity.getExchangeRate())
                    .reserveCharge(Objects.isNull(entity.getReserveCharge())?null:NumberUtil.getIntegerAmount(entity.getReserveCharge()))
                    .build()).collect(Collectors.toList());
        }else {
            return productAttrList;
        }
    }

    /**
     * 获取PC最低价位
     * @param productAttrList
     * @param h5
     * @return
     */
    private BigDecimal getMinPrice(List<LocalFunAttrDTO> h5Attrs, List<LocalFunAttrDTO> productAttrList,
                                   boolean h5) {
        if(h5){
            if(CollectionUtil.isNotEmpty(h5Attrs)){
                LocalFunAttrDTO localFunAttrDTO = h5Attrs.stream().min(Comparator.comparing(LocalFunAttrDTO::getPrice)).get();
                if(localFunAttrDTO != null){
                    return  NumberUtil.getIntegerAmount(localFunAttrDTO.getPrice());
                }else {
                    return BigDecimal.ZERO;
                }
            }
        }else {
            if(CollectionUtil.isNotEmpty(productAttrList)){
                LocalFunAttrDTO localFunAttrDTO = productAttrList.stream().min(Comparator.comparing(LocalFunAttrDTO::getPrice)).get();
                if(localFunAttrDTO != null){
                    return  NumberUtil.getIntegerAmount(localFunAttrDTO.getPrice());
                }else {
                    return BigDecimal.ZERO;
                }
            }
        }
        return BigDecimal.ZERO;
    }

    private List<ThemeJson> getTheme(LocalFunCandidateProductDto entity) {
        List<ThemeJson> list = Lists.newArrayListWithExpectedSize(10);
        if(StringUtil.isEmpty(entity.getThemeJson()) || entity.getThemeJson().contains("null")){
            return list;
        }
        List<String> themes = JSON.parseArray(entity.getThemeJson(), String.class);
        for(String theme : themes){
            ThemeJson themeJson = new ThemeJson();
            themeJson.setTheme(theme);
            list.add(themeJson);
        }
        return list;
    }

    /**
     * 获取PC规格信息
     * gzl-wansi
     * @param list
     * @return
     */
    private List<LocalFunAttrDTO> getPcAttrs(List<LocalFunCandidateProductDto> list) {
        List<LocalFunAttrDTO> attrList = Lists.newArrayListWithExpectedSize(10);
        //是否设置平台加辐
        boolean isChannelSet = list.stream().anyMatch(entity -> entity.getChannelId() != null);
        if(!isChannelSet){
//            addCandidateProduct(attrList, list);
            return attrList;
        }
        //PC
        List<LocalFunCandidateProductDto> pcProductList = list.stream().filter(entity->checkChannelSafe(entity)).filter(entity -> ChannelMainData.CHINA_TRAVEL.getId() == entity.getChannelId().longValue()).collect(Collectors.toList());
        addCandidateProduct(attrList, pcProductList);
        return attrList;
    }

    /**
     * 校验上架产品是否配置了渠道ID
     * @param entity
     */
    private boolean checkChannelSafe(LocalFunCandidateProductDto entity) {
        if(entity.getChannelId() == null){
            return false;
        }
        return true;
    }

    /**
     * 获取余票
     * gzl-wansi
     * @param dto
     * @return
     */
    private Integer getSolidNumber(LocalFunCandidateProductDto dto) {
        //1：限购售额
        if(dto.getIsLimit() != null && dto.getIsLimit() == 1){
            //今日可售額
            Integer limitNum = dto.getLimitNum();
            //產品总庫存
            Integer solidStock = dto.getSolidStock() == null ? 0 : dto.getSolidStock();

            if(limitNum == null){
                //餘票=庫存-產品已售額
                int surplusNumber = localFunProductSaleRecordMapper.selectSoldStockCount(dto.getProductId());
                return solidStock-surplusNumber;
            }else {
                //今日可售額 大於庫存，余票=总庫存-產品已售額
                if(limitNum > solidStock){
                    int surplusNumber = localFunProductSaleRecordMapper.selectSoldStockCount(dto.getProductId());
                    return solidStock-surplusNumber;
                }else {
                    if(limitNum == 0){
                        return 0;
                    }

                    int line; // 1.线上 2.线下
                    int channel; //1:自营 2:代理商 3:官方渠道 4:第三方
                    Long relationId;//线下自定义为customId = 1L 所有部门 ； customeId=2L 所有代理商 ； 其他则是关联ID,线上为各自的渠道ID

                    //线下渠道类型（1：自营渠道 2：代理商） ; null--官方平台、第三方平台（暂不考虑）
                    if(dto.getChannelType() == null){
                        //官方平台渠道
                        line = 1;
                        channel =3;
                        relationId = dto.getRelationId();
                    }else {
                        //自营渠道 已售数 1：自营渠道 2：代理商
                        //自营渠道 已售数
                        if(dto.getChannelType() == 1){
                            line = 2;
                            channel =1;
                            //选择所有部门 customId = 1L 所有部门;否则赋值关联ID
                            if(dto.getVisableType() == 2){
                                relationId = 1L;
                            }else {
                                relationId = dto.getRelationId();
                            }
                        }
                        //代理商渠道已售数
                        else {
                            line = 2;
                            channel =2;

                            //选择所有代理商 customId = 2L 所有部门;否则赋值关联ID
                            if(dto.getVisableType() == 2){
                                relationId = 2L;
                            }else {
                                relationId = dto.getRelationId();
                            }
                        }
                    }
                    int soldStockCount = localFunProductSaleRecordMapper.selectOnlineTodaySoldStockCount(dto.getProductId(),Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()),relationId,line,channel);
                    return limitNum -soldStockCount;
                }
            }
        }
        //2.不限售額，余票=总庫存-產品已售額
        else {
            Integer solidStock = dto.getSolidStock();
            //餘票=庫存-產品已售額
            int surplusNumber = localFunProductSaleRecordMapper.selectSoldStockCount(dto.getProductId());
            return solidStock-surplusNumber;
        }
    }

    /**
     * 获取H5规格信息
     * gzl-wansi
     * @param list
     * @return
     */
    private List<LocalFunAttrDTO> getH5Attrs(List<LocalFunCandidateProductDto> list) {
        List<LocalFunAttrDTO> attrList = Lists.newArrayListWithExpectedSize(10);
        //是否设置平台加辐
        boolean isChannelSet = list.stream().anyMatch(entity -> entity.getChannelId() != null);
        if(!isChannelSet){
//            addCandidateProduct(attrList, list);
            return attrList;
        }

        //APP&H5
        List<LocalFunCandidateProductDto> pcProductList = list.stream().filter(entity->checkChannelSafe(entity)).filter(entity -> ChannelMainData.APP_H5.getId() == entity.getChannelId().longValue()).collect(Collectors.toList());
        addCandidateProduct(attrList, pcProductList);
        return attrList;
    }

    /**
     * 添加设置产品价格信息
     * @param attrList
     * @param pcProductList
     */
    private void addCandidateProduct(List<LocalFunAttrDTO> attrList, List<LocalFunCandidateProductDto> pcProductList) {
        for (LocalFunCandidateProductDto dto : pcProductList) {
            //售卖价格 = 成本价*汇率+加辐费+预定服务费
            //预定服务费-百分比 = 成本价*汇率*百分比*0.01
            BigDecimal reserveCharge = Objects.isNull(dto.getReserveChargeType()) ? BigDecimal.ZERO : dto.getReserveChargeType() == 1 ? NumberUtil.getIntegerAmount(dto.getReserveCharge()) :
                    NumberUtil.getIntegerAmount(dto.getCostPrice().multiply(dto.getExchangeRate()).multiply(dto.getReserveCharge().multiply(new BigDecimal(0.01))));
            BigDecimal price = dto.getCostAndFee() != null ? dto.getCostAndFee().add(reserveCharge) : NumberUtil.getIntegerAmount(dto.getCostPrice().multiply(dto.getExchangeRate()).add(reserveCharge));
            attrList.add(LocalFunAttrDTO.builder().attr1_id(dto.getAttrId1())
                    .attr2_id(dto.getAttrId2())
                    .attr1Name(dto.getAttr1Name())
                    .attr2Name(dto.getAttr2Name())
                    .id(dto.getProductId()).price(NumberUtil.getIntegerAmount(price))
                    .costPrice(dto.getCostPrice())
                    .reserveCharge(reserveCharge)
                    .exchangeRate(dto.getExchangeRate())
                    .surplusStock(getSolidNumber(dto))
                    .build());
        }
    }

    /**
     * 当地玩乐 - 构建产品缓存数据
     */
    private List<LocalFunProductCacheUpDTO> builProductData(LocalFunProductCacheOpsReq req) {
        LambdaQueryWrapper<LocalFunProduct> productWrapper = Wrappers.<LocalFunProduct>lambdaQuery().eq(LocalFunProduct::getIsDeleted, IsDeletedCode.NO.getCode());
        productWrapper.eq(LocalFunProduct::getStatus, SaleOnShelfStatusCode.ON_SHELF_STATUS_1.getCode());
        productWrapper.eq(LocalFunProduct::getSaleStatus, SaleOnShelfStatusCode.SALE_STATUS_1.getCode());
        productWrapper.eq(LocalFunProduct::getApprovalStatus, ApprovalStatus.REVIEW_YES.getCode());
        if (req != null) {
            productWrapper.ge(req.getStartTime() != null, LocalFunProduct::getGmtModified, req.getStartTime());
            productWrapper.le(req.getEndTime() != null, LocalFunProduct::getGmtModified, req.getEndTime());
            productWrapper.in(req.getIds() != null, LocalFunProduct::getId, req.getIds());
        }

        List<LocalFunProduct> localFunProducts = localFunProductMapper.selectList(productWrapper);

        List<LocalFunProductCacheUpDTO> localFunProductCacheUpDTOS = new ArrayList<>();
        Assist.forEach(localFunProducts, product -> {
            Long productId = product.getId();
            LocalFunProductCacheUpDTO localFunProductCacheUpDTO = EntityUtil.copy(product, LocalFunProductCacheUpDTO.class);
            localFunProductCacheUpDTO.setCreateDate(product.getGmtCreate());
            localFunProductCacheUpDTO.setTitle(product.getName());
            // 补充产品销量信息
            int saleCount = localFunProductSaleRecordMapper.queryProductSaleCount(productId);
            localFunProductCacheUpDTO.setSaleCount(saleCount);

            // 补充产品规格价格信息
            // 加幅策略
            List<LocalFunProductCostStrategy> localFunProductCostStrategies = localFunProductCostStrategyMapper.selectList(Wrappers.<LocalFunProductCostStrategy>lambdaQuery().eq(LocalFunProductCostStrategy::getProductId, productId).eq(LocalFunProductCostStrategy::getIsDeleted, IsDeletedCode.NO.getCode()));
            List<Long> costIds = localFunProductCostStrategies.stream().map(item -> item.getId()).collect(Collectors.toList());
            if (costIds != null && costIds.size() > 0) {
                // 过滤有效期内的加幅策略
                LocalDateTime now = LocalDateTime.now();
                LocalFunProductCostStrategyVaildDate vaildDateCost = localFunProductCostStrategyVaildDateMapper.selectOne(Wrappers.<LocalFunProductCostStrategyVaildDate>lambdaQuery().eq(LocalFunProductCostStrategyVaildDate::getProductId,productId).in(LocalFunProductCostStrategyVaildDate::getCostId, costIds).le(LocalFunProductCostStrategyVaildDate::getTimeBegin, now).ge(LocalFunProductCostStrategyVaildDate::getTimeEnd, now));
                if (vaildDateCost != null) {
                    // 获取产品成本价列表
                    List<LocalFunProductCostStrategySpecStock> localFunProductCostStrategySpecStocks = localFunProductCostStrategySpecStockMapper.selectList(Wrappers.<LocalFunProductCostStrategySpecStock>lambdaQuery().eq(LocalFunProductCostStrategySpecStock::getProductId, productId).eq(LocalFunProductCostStrategySpecStock::getCostId, vaildDateCost.getCostId()).eq(LocalFunProductCostStrategySpecStock::getIsDeleted, IsDeletedCode.NO.getCode()));

                    // 产品是否存在成本价
                    if (localFunProductCostStrategySpecStocks != null && localFunProductCostStrategySpecStocks.size() > 0) {
                        // 获取产品渠道加幅策略
                        List<LocalFunProductChannelOnlineSelfPlatform> onlineSelfPlatforms = localFunProductChannelOnlineSelfPlatformMapper.selectList(Wrappers.<LocalFunProductChannelOnlineSelfPlatform>lambdaQuery().eq(LocalFunProductChannelOnlineSelfPlatform::getIsDeleted, IsDeletedCode.NO.getCode()).eq(LocalFunProductChannelOnlineSelfPlatform::getStrategyId, localFunProductCostStrategySpecStocks.get(0).getCostId()).eq(LocalFunProductChannelOnlineSelfPlatform::getProductId, productId));

                        List<LocalFunAttrDTO> pcAttrDTOS = new ArrayList<>();
                        List<LocalFunAttrDTO> h5AttrDTOS = new ArrayList<>();

                        for (LocalFunProductCostStrategySpecStock specStock : localFunProductCostStrategySpecStocks) {
                            LocalFunAttrDTO localFunAttrDTO = new LocalFunAttrDTO();
                            localFunAttrDTO.setId(specStock.getId());
                            localFunAttrDTO.setAttr1_id(specStock.getAttr1Id());
                            localFunAttrDTO.setAttr2_id(specStock.getAttr2Id());
                            localFunAttrDTO.setSurplusStock(specStock.getSurplusStock());

                            for (LocalFunProductChannelOnlineSelfPlatform selfPlatform : onlineSelfPlatforms) {
                                if (selfPlatform.getChannelId().longValue() == ChannelMainData.APP_H5.getId()) {
                                    // 获取产品H5价格
                                    localFunAttrDTO.setPrice(calculatePrice(selfPlatform, specStock.getCostPrice()));
                                    h5AttrDTOS.add(localFunAttrDTO);
                                } else {
                                    // 获取产品Pc价格
                                    localFunAttrDTO.setPrice(calculatePrice(selfPlatform, specStock.getCostPrice()));
                                    pcAttrDTOS.add(localFunAttrDTO);
                                }
                            }
                        }
                        localFunProductCacheUpDTO.setPcAttrs(pcAttrDTOS);
                        localFunProductCacheUpDTO.setH5Attrs(h5AttrDTOS);
                        localFunProductCacheUpDTOS.add(localFunProductCacheUpDTO);
                    }
                }
            }
        });
        return localFunProductCacheUpDTOS;
    }

    /**
     * 当地玩乐 - 构建支付详情缓存数据
     */
    private List<LocalFunProductCacheUpDTO> buildPayDetailsData(LocalFunProductCacheOpsReq req) {

        return null;
    }

    /**
     * 计算加幅价格
     *
     * @param selfPlatform
     * @param costPrice
     * @return
     */
    private BigDecimal calculatePrice(LocalFunProductChannelOnlineSelfPlatform selfPlatform, BigDecimal costPrice) {
        if (ProductCenterEnum.AMOUNT.getChannelCode().equals(selfPlatform.getAdjustType())) {
            costPrice = costPrice.add(Objects.isNull(selfPlatform.getAmount()) ? BigDecimal.ZERO : selfPlatform.getAmount());
        } else {
            costPrice = costPrice.add(costPrice.multiply(Objects.isNull(selfPlatform.getPercent()) ? BigDecimal.ZERO : new BigDecimal(selfPlatform.getPercent() * 0.01)));
        }
        return costPrice;
    }

    /**
     * 设置缓存 上架/更新 时间
     *
     * @param key
     */
    private void setUpdateTime(String key, LocalDateTime now) {
        redisTemplate.opsForValue().set(key, now.toString());
    }

    /**
     * 获取缓存 上架/更新 时间
     *
     * @param key
     * @return
     */
    private LocalDateTime getUpdateTime(String key) {
        return JSON.parseObject(redisTemplate.opsForValue().get(key), LocalDateTime.class);
    }

    /**
     * redis是否存在key
     *
     * @param key
     * @return
     */
    private boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 执行 K:V 缓存删除
     *
     * @param keys 需要删除的keys
     * @return
     */
    private Map<String, Boolean> deletString(List<String> keys) {
        Map<String, Boolean> map = new HashMap<>();
        keys.forEach(key -> {
            Boolean delete = redisTemplate.delete(key);
            map.put(key, delete);
        });
        return map;
    }

    //增加足迹
    private void addUserFootPoint(LocalFunProductDetailsResDTO details,Long detailId,Long userId) {
        try {
            if(userId != null) {
                UserFootPointReq req = new UserFootPointReq();
                req.setUserId(userId);
                req.setType(SystemBusinessType.LOCAL_FUN.getCode());
                req.setTitle(details.getTitle());
                req.setContent(details.getFunName());
                req.setCurrencyId(Currency.HKD.getCode());
                req.setCurrency(Currency.HKD.getCurrency());
                req.setPrice(details.getAttrs().get(0).getPrice());
                if(!CollectionUtil.isEmpty(details.getPhotosJson())) {
                    req.setCoverUrl(details.getPhotosJson().get(0));
                }
                req.setDetailId(detailId);
                userFootPointService.add(req);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 內部提供-套票
     * @param strategyType 加幅类型，1：自营；2：代理；3：官方平台；4：第三方平台；
     * @param productId 产品ID
     * @param departureDate 出发日期
     * @param relId 关联ID，strategyType(1：用户ID；2：代理ID；3：渠道ID；4：渠道ID)
     * @param attrId1   規格ID1
     * @param attrId2   規格ID2  倆個規格ID都空，則選擇默認最小價格展示
     * @return
     */
    public LocalFunPriceRemainderDTO findLocalFunSalePriceInfoHandle(int strategyType, Long productId, LocalDate departureDate, Long relId, Long attrId1, Long attrId2) {
        LocalFunPriceRemainderDTO priceRemainder = new LocalFunPriceRemainderDTO();
        //1.构建成本價+加幅費
        switch (strategyType){
            case 1:
                List<LocalFunAddPriceDTO> allAttrsPriceList =baseMapper.queryOffLineSalePrice(productId,departureDate);
                //校验产品用户是否设置加辐费
                checkUserCandidate(relId, allAttrsPriceList);
                //构建产品价格信息
                buildOfflinePriceInfo(attrId1, attrId2, priceRemainder, allAttrsPriceList, departureDate,null);
                break;
            case 2:
                List<LocalFunAddPriceDTO> agentPriceList =baseMapper.querySaleAgentPrice(productId,departureDate);
                if(CollectionUtils.isEmpty(agentPriceList)){
                    throw new BusinessException(SystemError.LOCAL_FUN_18026);
                }
                //构建产品价格信息
                buildOfflinePriceInfo(attrId1, attrId2, priceRemainder, agentPriceList,departureDate,relId);
                break;
            case 3:
                //获取官方平台产品渠道的加辐
                List<LocalFunAddPriceDTO> officeDtoList = baseMapper.querySaleOfficePrice(productId,departureDate,relId);
                if(CollectionUtils.isEmpty(officeDtoList)){
//                    obtainUnSetFee(departureDate, priceRemainder, baseMapper.queryNullSaleOfficePrice(productId, departureDate, null));
                    throw new BusinessException(SystemError.LOCAL_FUN_18026);
                }
                log.info("获取官方平台加辐信息：{}",JSON.toJSONString(officeDtoList));
                //构建产品价格信息
                buildOfflinePriceInfo(attrId1, attrId2, priceRemainder, officeDtoList, departureDate, 1L);
                break;
            case 4:
                //获取第三方平台产品渠道的加辐
                List<LocalFunAddPriceDTO> thirdDtoList = baseMapper.querySaleThirdPrice(productId,departureDate,relId);
                if(CollectionUtils.isEmpty(thirdDtoList)){
                    //未设置加辐费
//                    obtainUnSetFee(departureDate, priceRemainder, baseMapper.queryNullSaleThirdPrice(productId,departureDate,null));
//                    break;
                    throw new BusinessException(SystemError.LOCAL_FUN_18026);
                }
                //构建产品价格信息
                buildOfflinePriceInfo(attrId1, attrId2, priceRemainder, thirdDtoList, departureDate, 1L);
                break;
            default:
                break;
        }

        //2.设置預定服務費
        LocalFunProduct localFunProduct = localFunProductMapper.selectById(productId);
        if(localFunProduct == null){
            throw new BusinessException(SystemError.SHIP_PRODUCT_11006);
        }
        if(localFunProduct.getReserveChargeType() ==null || localFunProduct.getReserveCharge() == null){
            priceRemainder.setReserveCharge(BigDecimal.ZERO);
        }else {
            //1 金額
            if(localFunProduct.getReserveChargeType() == 1){
                priceRemainder.setReserveCharge(localFunProduct.getReserveCharge());
            }
            //百分比
            else {
                BigDecimal reserveCharge = priceRemainder.getCostPrice().multiply(localFunProduct.getReserveCharge().multiply(new BigDecimal(0.01)));
                priceRemainder.setReserveCharge(reserveCharge);
            }
        }
        return priceRemainder;
    }


    /**
     * 未设置加辐费，处理策略
     * @param departureDate
     * @param priceRemainder
     * @param localFunAddPriceDTOS
     */
    protected void obtainUnSetFee(LocalDate departureDate, LocalFunPriceRemainderDTO priceRemainder, List<LocalFunAddPriceDTO> localFunAddPriceDTOS) {
        //未设置加辐费
        List<LocalFunAddPriceDTO> nullSaleOfficePriceList = localFunAddPriceDTOS;
        LocalFunAddPriceDTO localFunAddPriceDTO = nullSaleOfficePriceList.stream().findFirst().orElse(null);
        if (localFunAddPriceDTO != null) {
            priceRemainder.setAttr1Name(localFunAddPriceDTO.getAttr1Name());
            priceRemainder.setAttr2Name(localFunAddPriceDTO.getAttr2Name());
            priceRemainder.setCostPrice(localFunAddPriceDTO.getCostPrice());
            priceRemainder.setExchangeRate(localFunAddPriceDTO.getExchangeRate());
            priceRemainder.setPrice(Objects.isNull(localFunAddPriceDTO.getCostPrice()) ? null : NumberUtil.getDecimalPercent(localFunAddPriceDTO.getCostPrice().multiply(localFunAddPriceDTO.getExchangeRate())));
            priceRemainder.setSolidNumber(querySolidNumber(localFunAddPriceDTO, departureDate));
        }
    }

    /**
     * 校验该产品用户是否设置产品加辐费
     * @param relId
     * @param allAttrsPriceList
     */
    protected void checkUserCandidate(Long relId, List<LocalFunAddPriceDTO> allAttrsPriceList) {
        if(CollectionUtils.isNotEmpty(allAttrsPriceList)){
            LocalFunAddPriceDTO localFunAddPriceDTO = allAttrsPriceList.stream().findFirst().orElse(null);
            if(localFunAddPriceDTO != null){
                if(localFunAddPriceDTO.getVisableType() == 3){
                    if(relId == null){
                        throw new BusinessException(SystemError.LOCAL_FUN_18020);
                    }
                    List<String> relationIds = getUserIdsByUserId(relId);
                    allAttrsPriceList = allAttrsPriceList.stream().filter(entity->relationIds.contains(entity.getRelationId())).collect(Collectors.toList());
                }
            }
        }
        if(CollectionUtils.isEmpty(allAttrsPriceList)){
            throw new BusinessException(SystemError.LOCAL_FUN_18026);
        }
    }

    /**
     * 構建線下自營渠道 價格
     * @param attrId1
     * @param attrId2
     * @param priceRemainder
     * @param allAttrsPriceList
     * @param departureDate
     * @param relId
     */
    protected void buildOfflinePriceInfo(Long attrId1, Long attrId2, LocalFunPriceRemainderDTO priceRemainder, List<LocalFunAddPriceDTO> allAttrsPriceList, LocalDate departureDate, Long relId) {
        //不指定規格，默認查詢最小價格
        if(attrId1 == null && attrId2 == null){
            LocalFunAddPriceDTO minPriceDto = allAttrsPriceList.stream().min(Comparator.comparing(LocalFunAddPriceDTO::getCostAndFee)).get();
            if(minPriceDto != null){
                priceRemainder.setAttr1Name(minPriceDto.getAttr1Name());
                priceRemainder.setAttr2Name(minPriceDto.getAttr2Name());
                priceRemainder.setCostPrice(minPriceDto.getCostPrice());
                priceRemainder.setExchangeRate(minPriceDto.getExchangeRate());
                priceRemainder.setPrice(Objects.isNull(minPriceDto.getCostAndFee()) ? null :NumberUtil.getDecimalPercent(minPriceDto.getCostAndFee()));
                priceRemainder.setSolidNumber(querySolidNumber(minPriceDto,departureDate));
                return;
            }
        }

        //構建指定規格價格
        log.info("attrId1={},attrId2={}",attrId1,attrId2);
        //構建指定規格價格
        LocalFunAddPriceDTO specialPriceDto = null;
        if(relId == null || relId == 1L){
            specialPriceDto = allAttrsPriceList.stream().filter(entity -> {
                Boolean exactly = false;
                if (attrId1 != null && attrId2 != null) {
                    exactly = entity.getAttr1Id().equals(attrId1) && entity.getAttr2Id().equals(attrId2);
                }
                if (attrId1 != null && attrId2 == null) {
                    exactly = entity.getAttr1Id().equals(attrId1);
                }
                return exactly;
            }).findFirst().orElse(null);
        }else {
            LocalFunAddPriceDTO localFunAddPriceDTO = allAttrsPriceList.stream().findFirst().orElse(null);
            if(localFunAddPriceDTO != null){
                if(localFunAddPriceDTO.getVisableType() == 2){
                    specialPriceDto = allAttrsPriceList.stream().filter(entity -> {
                        Boolean exactly = false;
                        if (attrId1 != null && attrId2 != null) {
                            exactly = entity.getAttr1Id().equals(attrId1) && entity.getAttr2Id().equals(attrId2);
                        }
                        if (attrId1 != null && attrId2 == null) {
                            exactly = entity.getAttr1Id().equals(attrId1);
                        }
                        return exactly;
                    }).findFirst().orElse(null);
                }else {
                    specialPriceDto = allAttrsPriceList.stream().filter(entity -> {
                        Boolean exactly = false;
                        if (attrId1 != null && attrId2 != null) {
                            exactly = entity.getAttr1Id().equals(attrId1) && entity.getAttr2Id().equals(attrId2);
                        }
                        if (attrId1 != null && attrId2 == null) {
                            exactly = entity.getAttr1Id().equals(attrId1);
                        }
                        return exactly;
                    }).filter(en -> relId.equals(en.getRelationId())).findFirst().orElse(null);
                }
            }
        }

        log.info("对应价格信息：{}",JSON.toJSONString(specialPriceDto));

        if(specialPriceDto != null){
            priceRemainder.setAttr1Name(specialPriceDto.getAttr1Name());
            priceRemainder.setAttr2Name(specialPriceDto.getAttr2Name());
            priceRemainder.setCostPrice(specialPriceDto.getCostPrice());
            priceRemainder.setExchangeRate(specialPriceDto.getExchangeRate());
            priceRemainder.setPrice(NumberUtil.getDecimalPercent(specialPriceDto.getCostAndFee()));
            priceRemainder.setSolidNumber(querySolidNumber(specialPriceDto, departureDate));
            return;
        }
    }


    /**
     * 获取余票
     * @param specialPriceDto
     * @param departureDate
     * @return
     */
    private Integer querySolidNumber(LocalFunAddPriceDTO specialPriceDto, LocalDate departureDate) {
        //1：限购售额
        if(specialPriceDto.getIsLimit() != null && specialPriceDto.getLimitNum() != null && specialPriceDto.getIsLimit() == 1){
            //今日可售額
            Integer limitNum = specialPriceDto.getLimitNum();
            //產品总庫存
            Integer solidStock = specialPriceDto.getSolidStock();

            //今日可售額 大於庫存，余票=总庫存-產品已售額
            if(limitNum > solidStock){
                int surplusNumber = saleRecordMapper.selectSoldStockCount(specialPriceDto.getProductId());
                return solidStock-surplusNumber;
            }else {
                if(limitNum == null || limitNum == 0){
                    throw new BusinessException(SystemError.SHIP_PRODUCT_11051);
                }
                //餘票=今日可售額-今日已售額
                int soldStockCount = saleRecordMapper.selectTodaySoldStockCount(specialPriceDto.getProductId(),Date.from(departureDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
                return limitNum -soldStockCount;
            }
        }
        //2.不限售額，余票=总庫存-產品已售額
        else {
            Integer solidStock = specialPriceDto.getSolidStock();
            //餘票=庫存-產品已售額
            int surplusNumber = saleRecordMapper.selectSoldStockCount(specialPriceDto.getProductId());
            return solidStock-surplusNumber;
        }
    }


    private List<String> getUserIdsByUserId(Long id) {
        List<Long> longs = iSysDepartmentService.queryIdsByUserId(id);
        longs.add(id);
        String stringBuffer = "";
        for (Long aLong : longs) {
            stringBuffer += aLong.toString() + ",";
        }
        if (!"".equals(stringBuffer)) {
            stringBuffer = stringBuffer.substring(0, stringBuffer.length() - 1);
        }
        return Lists.newArrayList(stringBuffer.split(","));
    }
}